﻿using DotNetCore.CAP;
using DotNetCore.CAP.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace TK.Common.CAP
{
    /// <summary>
    /// cap CustomConsumerServiceSelector
    /// </summary>
    public class CustomConsumerServiceSelector : ConsumerServiceSelector
    {
        private readonly CapOptions _capOptions;

        /// <summary>
        /// ctor
        /// </summary>
        public CustomConsumerServiceSelector(IServiceProvider serviceProvider)
            : base(serviceProvider)
        {
            _capOptions = serviceProvider.GetRequiredService<IOptions<CapOptions>>().Value;
        }

        /// <summary>
        /// FindConsumersFromInterfaceTypes
        /// </summary>
        protected override IEnumerable<ConsumerExecutorDescriptor> FindConsumersFromInterfaceTypes(IServiceProvider provider)
        {
            var list = new List<ConsumerExecutorDescriptor>();
            var typeInfo = typeof(ICapSubscribe).GetTypeInfo();
            foreach (var item in from o in provider.GetRequiredService<IServiceCollection>()
                                 where o.ImplementationType != null || o.ImplementationFactory != null
                                 select o)
            {
                var c = item.ImplementationType ?? item.ServiceType;
                if (typeInfo.IsAssignableFrom(c))
                {
                    var orginType = item.ImplementationType;
                    if (orginType == null)
                    {
                        throw new NullReferenceException("ServiceType");
                    }

                    using (var scope = provider.CreateScope())
                    {
                        var proxyType = scope.ServiceProvider.GetRequiredService(item.ServiceType).GetType();
                        list.AddRange(GetTopicAttributesDescription(orginType.GetTypeInfo(), proxyType.GetTypeInfo(), item.ServiceType.GetTypeInfo()));
                    }
                }
            }

            return list;
        }

        /// <summary>
        /// cap TopicAttributes
        /// </summary>
        protected IEnumerable<ConsumerExecutorDescriptor> GetTopicAttributesDescription(TypeInfo typeInfo, TypeInfo proxyTypeInfo, TypeInfo serviceTypeInfo = null)
        {
            var topicClassAttribute = typeInfo.GetCustomAttribute<TopicAttribute>(true);
            var originMethods = typeInfo.DeclaredMethods.ToList();
            var proxyMethods = proxyTypeInfo.DeclaredMethods.ToList();
            MethodInfo orginMethod = null;
            foreach (var method in proxyMethods)
            {
                orginMethod = originMethods.FirstOrDefault(m => m.Name == method.Name);
                if (orginMethod != null)
                {
                    var enumerable = orginMethod.GetCustomAttributes<TopicAttribute>(true);
                    if (topicClassAttribute == null)
                    {
                        enumerable = enumerable.Where((TopicAttribute x) => !x.IsPartial && x.Name != null);
                    }

                    if (!enumerable.Any())
                    {
                        continue;
                    }

                    foreach (var item in enumerable)
                    {
                        SetSubscribeAttribute(item);
                        var parameters = (from parameter in orginMethod.GetParameters()
                                          select new ParameterDescriptor
                                          {
                                              Name = parameter.Name,
                                              ParameterType = parameter.ParameterType,
                                              IsFromCap = parameter.GetCustomAttributes(typeof(FromCapAttribute)).Any() || typeof(CancellationToken).IsAssignableFrom(parameter.ParameterType)
                                          }).ToList();
                        yield return InitDescriptor(item, method, proxyTypeInfo, serviceTypeInfo, parameters, topicClassAttribute);
                    }
                }
            }
        }

        #region prvate methods
        /// <summary>
        /// ConsumerExecutorDescriptor create
        /// </summary>
        private ConsumerExecutorDescriptor InitDescriptor(TopicAttribute attr, MethodInfo methodInfo, TypeInfo implType, TypeInfo serviceTypeInfo, IList<ParameterDescriptor> parameters, TopicAttribute classAttr = null)
        {
            return new ConsumerExecutorDescriptor
            {
                Attribute = attr,
                ClassAttribute = classAttr,
                MethodInfo = methodInfo,
                ImplTypeInfo = implType,
                ServiceTypeInfo = serviceTypeInfo,
                Parameters = parameters,
                TopicNamePrefix = _capOptions.TopicNamePrefix
            };
        }
        #endregion
    }
}
